﻿using System;
using System.Collections.Generic;
using System.Linq;
using CommonBehaviors.Actions;
using Portal.Helpers;
using Portal.Managers;
using Portal.Settings;
using Styx;
using Styx.CommonBot;
using Styx.CommonBot.POI;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

namespace Portal.Core
{
    internal static class Unit
    {
        #region Core Unit Checks - these should all be incorperated into the individual class checks below.

        private static LocalPlayer Me { get { return StyxWoW.Me; } }

        internal static IEnumerable<WoWUnit> AttackableUnits
        {
            get
            {
                try
                {
                    var unitList = ObjectManager.GetObjectsOfTypeFast<WoWUnit>().Where(
                        u =>
                        u != null && u.IsValid && !u.IsDead && u.Attackable && u.CanSelect && !u.IsNonCombatPet &&
                        !u.IsCritter && u.DistanceSqr <= 3600);

                    return unitList.Where(u => !u.IsFriendly && !SpellLists.IgnoreUnits.Contains(u.Entry));
                }
                catch(Exception ex)
                {
                    Log.Debug("Exception thrown at Unit.AttackableUnits: {0}", ex);
                    return null;
                }
            }
        }

        internal static IEnumerable<WoWPlayer> GroupMembers
        {
            get { return ObjectManager.GetObjectsOfType<WoWPlayer>(true, true).Where(u => !u.IsDead && u.CanSelect && u.IsInMyPartyOrRaid); }
        }

        internal static IEnumerable<WoWUnit> NearbyAttackableUnits(WoWPoint fromLocation, double radius)
        {
            var hostile = AttackableUnits;
            var maxDistance = radius*radius;
            return hostile.Where(x => x.Location.DistanceSqr(fromLocation) < maxDistance); 
        }

        internal static IEnumerable<WoWUnit> NearbyAttackableUnitsAttackingUs(WoWPoint fromLocation, double radius)
        {
            var hostile = AttackableUnits;
            var maxDistance = radius * radius;
            return hostile.Where(x => x.Location.DistanceSqr(fromLocation) < maxDistance && x.IsTargetingUs());
        }

        internal static IEnumerable<WoWUnit> AttackableUnitsAll
        {
            get { return ObjectManager.GetObjectsOfType<WoWUnit>(true, false).Where(u => !u.IsDead && u.CanSelect && u.Attackable && !u.IsFriendly && !u.IsNonCombatPet && !u.IsCritter); }
        }
        

        #endregion

        
        #region MultiDotting

        internal static WoWUnit GetMultiDoTTarget(WoWUnit unit, string debuff, double radius, double refreshDurationRemaining)
        {
            // find unit without our debuff
            var dotTarget = NearbyAttackableUnitsAttackingUs(unit.Location, radius)
                .Where(x => x != null && !x.IsTotem())
                .OrderByDescending(x => x.HealthPercent)
                .FirstOrDefault(x => !x.HasMyAura(debuff) && !x.HasDoNotDamageAura() && x.InLineOfSpellSight);

            if (dotTarget == null)
            {
                // If we couldn't find one without our debuff, then find ones where debuff is about to expire.
                dotTarget = NearbyAttackableUnitsAttackingUs(unit.Location, radius)
                            .Where(x => x != null && !x.IsTotem())
                            .OrderByDescending(x => x.HealthPercent)
                            .FirstOrDefault(x => (x.HasMyAura(debuff) && !x.HasDoNotDamageAura() && Spell.GetMyAuraTimeLeft(debuff, x) < refreshDurationRemaining) && x.InLineOfSpellSight);
            }
            return dotTarget;
        }

        #endregion MultiDotting

        #region Extensions

        internal static bool IsBoss(this WoWUnit thisUnit)
        {
            return thisUnit != null && (thisUnit.IsBoss || BossList.BossIds.Contains(thisUnit.Entry));
        }

        internal static bool IsCrowdControlled(this WoWUnit unit)
        {
            if (unit != null)
            {
                Dictionary<string, WoWAura>.ValueCollection auras = unit.Auras.Values;

                return auras.Any(a => a.IsHarmful && 
                    (SpellLists.CrowdControlList.Contains(a.SpellId) ||
                    
                    (a.Spell.Mechanic == WoWSpellMechanic.Banished ||
                    a.Spell.Mechanic == WoWSpellMechanic.Disoriented ||
                    a.Spell.Mechanic == WoWSpellMechanic.Charmed ||
                    a.Spell.Mechanic == WoWSpellMechanic.Horrified ||
                    a.Spell.Mechanic == WoWSpellMechanic.Incapacitated ||
                    a.Spell.Mechanic == WoWSpellMechanic.Polymorphed ||
                    a.Spell.Mechanic == WoWSpellMechanic.Sapped ||
                    a.Spell.Mechanic == WoWSpellMechanic.Shackled ||
                    a.Spell.Mechanic == WoWSpellMechanic.Asleep ||
                    a.Spell.Mechanic == WoWSpellMechanic.Frozen ||
                    a.Spell.Mechanic == WoWSpellMechanic.Invulnerable ||
                    a.Spell.Mechanic == WoWSpellMechanic.Invulnerable2 ||
                    a.Spell.Mechanic == WoWSpellMechanic.Turned ||
                    a.Spell.Mechanic == WoWSpellMechanic.Fleeing))
                                                    
                                                    );
            }
            return false;
        }

        internal static bool InBGOrArena(this WoWUnit unit)
        {
            return Me.CurrentMap.IsBattleground || Me.CurrentMap.IsArena;
        }

        internal static bool HasDoNotDamageAura(this WoWUnit unit)
        {
            if (unit != null)
            {
                Dictionary<string, WoWAura>.ValueCollection auras = unit.Auras.Values;

                return auras.Any(a => a.IsHarmful && SpellLists.DoNotDamageList.Contains(a.SpellId));
            }
            return false;
        }

        internal static bool PlayerHasMeleeOffensiveCooldown(this WoWUnit unit)
        {
            if (unit != null)
            {
                Dictionary<string, WoWAura>.ValueCollection auras = unit.Auras.Values;
                return auras.Any(a => a.IsHarmful && SpellLists.MeleeDisarmCooldown.Contains(a.SpellId));
            }
            return false;
        }

        internal static bool HasOffensiveCooldown(this WoWUnit unit)
        {
            if (unit != null)
            {
                Dictionary<string, WoWAura>.ValueCollection auras = unit.Auras.Values;

                return auras.Any(a => a.IsHarmful && SpellLists.PlayerCooldown.Contains(a.SpellId));
            }
            return false;
        }

        internal static bool HasSilenceImmunityAura(this WoWUnit unit)
        {
            if (unit != null)
            {
                Dictionary<string, WoWAura>.ValueCollection auras = unit.Auras.Values;

                return auras.Any(a => a.IsHarmful && SpellLists.UnableToSilenceAura.Contains(a.SpellId));
            }
            return false;
        }
        
        private static bool IsAerialTarget(this WoWUnit unit)
        {
            if (unit.MaxHealth == 1) return false; // training dummy..:/
            float height = HeightOffTheGround(unit);
            if (height > 5f && height < float.MaxValue)
                return true;
            return false;
        }

        private static float HeightOffTheGround(this WoWUnit unit)
        {
            float minDiff = float.MaxValue;
            var pt = new WoWPoint(unit.Location.X, unit.Location.Y, unit.Location.Z);

            List<float> listMeshZ = Navigator.FindHeights(pt.X, pt.Y);
            foreach (var meshZ in listMeshZ)
            {
                var diff = pt.Z - meshZ;
                if (diff >= 0 && diff < minDiff)
                {
                    minDiff = diff;
                    pt.Z = meshZ;
                }
            }

            return minDiff;
        }

        /*
         
        * Example Code for integrating a Hotkey for cooldown usage!
         
        */
        internal static bool IsBoss(this WoWUnit thisUnit, bool hotkey)
        {
            return thisUnit != null && (hotkey || thisUnit.IsBoss || BossList.BossIds.Contains(thisUnit.Entry));
        }

        internal static bool IsBossOrPlayer(this WoWUnit thisUnit)
        {
            return thisUnit != null && ((thisUnit.IsPlayer && !thisUnit.IsFriendly) || thisUnit.IsBoss || BossList.BossIds.Contains(thisUnit.Entry));
        }

        internal static bool IsTotem(this WoWUnit unit)
        {
            return unit != null &&
                   (unit.IsTotem || unit.Name.Contains("Totem") || SpellLists.TotemList.Contains(unit.Entry));
        }

        internal static bool IsStunned(this WoWUnit unit)
        {
            return unit != null && 
                (unit.Stunned || unit.HasAuraWithMechanic(WoWApplyAuraType.ModStun));
        }

        internal static bool IsRooted(this WoWUnit unit)
        {
            return unit != null &&
                   (unit.Rooted || unit.HasAuraWithMechanic(WoWApplyAuraType.ModRoot));
        }
        
        public static void CancelAura(this WoWUnit unit, string aura)
        {
            WoWAura a = unit.GetAuraByName(aura);
            if (a != null && a.Cancellable)
                a.TryCancelAura();
        }


        #endregion

        #region Aquire Units
        public static WoWUnit MouseoverUnit()
        {
            var unitGuid = Helpers.Lua.MouseOverUnitGuid();
            if (unitGuid != "0")
            {
                return ObjectManager.GetObjectsOfTypeFast<WoWUnit>()
                                 .FirstOrDefault(u => string.Format("0x{0:X}", u.Guid) == unitGuid);
            }
            return null;
        }
        #endregion

        #region Targeting

        public static bool IsTargetingUs(this WoWUnit unit)
        {
            return (unit.IsTargetingMyPartyMember || unit.IsTargetingMeOrPet || unit.IsTargetingAnyMinion ||
                    unit.IsTargetingMyRaidMember || unit.IsTargetingPet || unit.IsTargetDummy());
        }

        public static bool IsTargetDummy(this WoWUnit unit)
        {

            return unit != null && SpellLists.TargetDummies.Contains(unit.Entry);
        }
        

        #endregion

    }
}
